#include "network.h"
#include "globals.h"
#include "config.h"
#include "player.h"
#include "npc.h"

Network::Network()
{
    client = RakPeerInterface::GetInstance();
    client->Startup(1, &SocketDescriptor(), 1);
    connected = false;
}

Network::~Network()
{

}

bool Network::connect(std::string destIP, int destPort)
{
    ip = destIP;
    port = destPort;
    connected = false;
    int tries = 0;

    output.write("Connecting to server %s:%i", destIP.c_str(), destPort);
	client->Connect(destIP.c_str(), destPort, 0, 0);

	ConnectionState status;

	do
	{
	    if (tries > 100)
            return false;

	    receive_data();
	    status = client->GetConnectionState(RakNet::SystemAddress(destIP.c_str(), destPort));

	    SDL_Delay(100);
	    tries++;
	}
	while (!connected);

	return true;
}

void Network::disconnect()
{
    client->Shutdown(500);
    RakPeerInterface::DestroyInstance(client);
}

unsigned char Network::GetPacketIdentifier(Packet *p)
{
	if (p == 0)
		return 255;

	if ((unsigned char)p->data[0] == ID_TIMESTAMP)
	{
		RakAssert(p->length > sizeof(MessageID) + sizeof(Time));
		return (unsigned char) p->data[sizeof(MessageID) + sizeof(Time)];
	}
	else
        return (unsigned char) p->data[0];
}

void Network::send_message(const RakNet::BitStream *bitStream, PacketPriority priority, PacketReliability reliability)
{
    client->Send(bitStream, priority, reliability, 0, RakNet::UNASSIGNED_SYSTEM_ADDRESS, true);
}

void Network::receive_data()
{
    for (packet=client->Receive(); packet; client->DeallocatePacket(packet), packet=client->Receive())
    {
        packetIdentifier = GetPacketIdentifier(packet);

        switch (packetIdentifier)
        {
            case ID_ACCOUNT_CREATE_FAILED:
            {
                output.write("Create failed");

                login.stopProcessing();
                login.account_menu(0);
                break;
            }

            case ID_ACCOUNT_CREATE_OK:
            {
                output.write("Create ok");

                login.stopProcessing();
                login.account_menu(0);
                break;
            }

            case ID_ACCOUNT_LOGIN_FAILED:
            {
                output.write("Login failed");

                login.stopProcessing();
                login.account_menu(2);
                break;
            }

            case ID_ACCOUNT_LOGIN_OK:
            {
                int accountID;

                output.write("Login ok");

                BitStream bsIn(packet->data, packet->length, false);
                bsIn.IgnoreBytes(sizeof(MessageID));
                bsIn.Read(accountID);

                //output.write("ACCOUNT ID (OWNER) IS %i", accountID);

                //login.get_characters();

                login.setAccountID(accountID);
                login.stopProcessing();
                login.character_menu();

                break;
            }

            case ID_CHARACTER_CREATE_OK:
            {
                output.write("Create ok");

                login.stopProcessing();
                login.character_menu();

                break;
            }

            case ID_CHARACTER_CREATE_FAILED:
            {
                output.write("Create failed");

                login.stopProcessing();
                login.character_menu();

                break;
            }

            case ID_CHARACTER_INFO:
            {
                RakString name;
                int id;
                int type;
                int level;

                BitStream bsIn(packet->data, packet->length, false);

                bsIn.IgnoreBytes(sizeof(MessageID));
                bsIn.Read(id);
                bsIn.Read(name);
                bsIn.Read(type);
                bsIn.Read(level);

                //output.write("CHARACTER: %s class:%i level:%i", name.C_String(), type, level);
                login.addCharacter(id, std::string(name), type, level);

                break;
            }

            case ID_CONNECTION_ATTEMPT_FAILED:
                connected = false;
                break;

            case ID_CONNECTION_REQUEST_ACCEPTED:
            {
                output.write("Connected successfully!");
                connected = true;

                // Use a BitStream to write a custom user message
                // Bitstreams are easier to use than sending casted structures, and handle endian swapping automatically
                /*BitStream bsOut;
                bsOut.Write((MessageID)ID_PLAYER_JOIN);
                bsOut.Write(cfg.get_username().c_str());
                client->Send(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, packet->systemAddress, false);*/
                break;
            }


            /*case ID_NEW_INCOMING_CONNECTION:
                printf("A connection is incoming.\n");
                break;
            case ID_NO_FREE_INCOMING_CONNECTIONS:
                printf("The server is full.\n");
                break;*/

            case ID_DISCONNECTION_NOTIFICATION:
                output.write("Disconnected from the server!");
                connected = false;
                Npc::removeNpcs();
                break;

            case ID_CONNECTION_LOST:
                output.write("Connection lost!");
                connected = false;
                Npc::removeNpcs();
                break;

            case ID_PLAYER_JOIN:
            {
                /* Get info of the joined player */

                RakString name;
                RakNet::RakNetGUID guid;

                BitStream bsIn(packet->data, packet->length, false); // The false is for efficiency so we don't make a copy of the passed data

                bsIn.IgnoreBytes(sizeof(MessageID));
                bsIn.Read(name);
                bsIn.Read(guid);

                Player *newPlayer = new Player(guid, std::string(name), 128, 128);

                output.write("%s (%s) joined the game!", name.C_String(), guid.ToString());

                break;
            }


            case ID_PLAYER_PART:
            {
                RakNet::RakNetGUID guid;
                MessageID type;
                BitStream bsIn(packet->data, packet->length, false); // The false is for efficiency so we don't make a copy of the passed data

                bsIn.IgnoreBytes(sizeof(MessageID));
                bsIn.Read(type);
                bsIn.Read(guid);

                Player* player = Player::GetPlayerFromGUID(guid);

                if (player == NULL)
                    break;

                delete player;

                switch (type)
                {
                    case ID_QUIT_DISCONNECT:
                        output.write("%s (%s) left the game (disconnected)", player->get_name().c_str(), guid.ToString());
                        break;
                    case ID_QUIT_CONNECTION_LOST:
                        output.write("%s (%s) left the game (lost connection)", player->get_name().c_str(), guid.ToString());
                        break;
                }

                break;
            }


            case ID_PLAYER_UPDATE:
            {
                /* Get data about the player */

                RakNet::BitStream bsIn(packet->data, packet->length, false); // The false is for efficiency so we don't make a copy of the passed data
                bsIn.IgnoreBytes(sizeof(RakNet::MessageID) * 2);

                /*switch (packet->data[1])
                {
                    case ID_PLAYER_POS:
                    {
                        RakString guid;
                        int x, y;

                        bsIn.Read(x);
                        bsIn.Read(y);
                        bsIn.Read(guid);

                        for (vector<Player*>::iterator i = vPlayer.begin(); i != vPlayer.end(); i++)
                        {
                            if (strcmp((*i)->get_guid().c_str(), guid) == 0)
                            {
                                (*i)->set_x(x);
                                (*i)->set_y(y);

                                output.write("%s's position changed to %d,%d", (*i)->get_name().c_str(), x, y);
                            }
                        }
                        break;
                    }

                    case ID_PLAYER_HP:
                    {
                        RakString guid;
                        int hp;

                        bsIn.Read(hp);
                        bsIn.Read(guid);

                        for (vector<Player*>::iterator i = vPlayer.begin(); i != vPlayer.end(); i++)
                        {
                            if (strcmp((*i)->get_guid().c_str(), guid) == 0)
                            {
                                (*i)->hp = hp;

                                output.write("%s's hp changed to %d", (*i)->get_name().c_str(), hp);
                            }
                        }
                        break;
                    }

                    case ID_PLAYER_MAXHP:
                    {
                        RakString guid;
                        int maxhp;

                        bsIn.Read(maxhp);
                        bsIn.Read(guid);

                        for (vector<Player*>::iterator i = vPlayer.begin(); i != vPlayer.end(); i++)
                        {
                            if (strcmp((*i)->get_guid().c_str(), guid) == 0)
                            {
                                (*i)->maxhp = maxhp;

                                output.write("%s's maxhp changed to %d", (*i)->get_name().c_str(), maxhp);
                            }
                        }
                        break;
                    }

                    case ID_PLAYER_MP:
                    {
                        RakString guid;
                        int mp;

                        bsIn.Read(mp);
                        bsIn.Read(guid);

                        for (vector<Player*>::iterator i = vPlayer.begin(); i != vPlayer.end(); i++)
                        {
                            if (strcmp((*i)->get_guid().c_str(), guid) == 0)
                            {
                                (*i)->mp = mp;

                                output.write("%s's mp changed to %d", (*i)->get_name().c_str(), mp);
                            }
                        }
                        break;
                    }

                    case ID_PLAYER_MAXMP:
                    {
                        RakString guid;
                        int maxmp;

                        bsIn.Read(maxmp);
                        bsIn.Read(guid);

                        for (vector<Player*>::iterator i = vPlayer.begin(); i != vPlayer.end(); i++)
                        {
                            if (strcmp((*i)->get_guid().c_str(), guid) == 0)
                            {
                                (*i)->maxmp = maxmp;

                                output.write("%s's maxmp changed to %d", (*i)->get_name().c_str(), maxmp);
                            }
                        }
                        break;
                    }

                    default:
                        break;
                }*/

                break;
            }

            case ID_PLAYER_FLAG:
            {
                RakNet::RakNetGUID guid;
                int x, y;

                MessageID useTimeStamp; // Assign this to ID_TIMESTAMP
                Time timeStamp; // Put the system time in here returned by GetTime()
                MessageID typeId; // This will be assigned to a type I've added after ID_USER_PACKET_ENUM, lets say ID_SET_TIMED_MINE
                BitStream bsIn(packet->data, packet->length, false); // The false is for efficiency so we don't make a copy of the passed data

                bsIn.Read(useTimeStamp);
                bsIn.Read(timeStamp);
                bsIn.Read(typeId);

                //bsIn.IgnoreBytes(sizeof(MessageID));

                bsIn.Read(x);
                bsIn.Read(y);
                bsIn.Read(guid);

                Player* player = Player::GetPlayerFromGUID(guid);

                if (player == NULL)
                    break;

                player->create_flag(x, y);
                //output.write("%s created flag at position %i,%i", player->get_name().c_str(), x, y);

                /*for (vector<Player*>::iterator i = vPlayer.begin(); i != vPlayer.end(); i++)
                {
                    if (strcmp((*i)->get_guid().c_str(), guid) == 0)
                    {
                        (*i)->create_flag(x, y);

                        output.write("%s created flag at position %i,%i", (*i)->get_name().c_str(), x, y);
                    }
                }*/
                break;
            }

            case ID_NPC_SPAWN:
            {
                int id, x, y;
                //std::string name;
                RakString name;

                BitStream bsIn(packet->data, packet->length, false);

                bsIn.IgnoreBytes(sizeof(MessageID));

                bsIn.Read(id);
                bsIn.Read(name);
                bsIn.Read(x);
                bsIn.Read(y);

                Npc *newNpc = new Npc(id, (std::string)name, x, y);

                //vNpc.push_back(newNpc);

                output.write("%s (%d) spawned at %d,%d", name.C_String(), id, x, y);

                break;
            }

            case ID_NPC_FLAG:
            {
                int id, x, y;

                BitStream bsIn(packet->data, packet->length, false);

                bsIn.IgnoreBytes(sizeof(MessageID));

                bsIn.Read(id);
                bsIn.Read(x);
                bsIn.Read(y);

                Npc* npc = Npc::GetNpcFromID(id);
                if (npc == NULL)
                    break;

                npc->create_flag(x, y);

                //output.write("NPC id %i created flag at position %i, %i", id, x, y);

                break;
            }

            default:
                output.write("Message with identifier %i has arrived", packet->data[0]);
                break;
        }
    }
}
